﻿using System;
using System.Collections.Generic;
using Firefly.ExtensionMethods;
using Firefly.Framework.Input;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Firefly.Framework.Windows.Forms
{
    public class Memo : Control
    {
        protected int _selectStart = 0;
        protected int _selectEnd = 0;
        private Rectangle _textBoxArea;
        private Rectangle _textArea;
        private List<String> _allLines;
        private Scrollbar _verticalScrollbar;
        private string _displayText = string.Empty;
        private List<String> _displayLines = new List<string>();
        private float _lineHeight = 0;
        private int _scrollSleep = 0;

        [ContentSerializer(Optional = true)]
        public Color ForeColorHighlight { get; set; }
        [ContentSerializer(Optional = true)]
        public Color BackColorHighlight { get; set; }
        [ContentSerializer(Optional = true)]
        public bool Locked { get; set; }
        [ContentSerializer(Optional = true)]
        public int SelectEnd
        {
            get { return _selectEnd; }
            set
            {
                _selectEnd = value;
                ScrollToCursor();
            }
        }
        [ContentSerializer(Optional = true)]
        public int SelectStart
        {
            get { return _selectStart; }
            set
            {
                _selectStart = value;
                ScrollToCursor();
            }
        }
        protected bool CursorBlink { get; set; }
        protected int CursorTime { get; set; }
        protected bool Selecting { get; set; }

        public Memo()
            : base()
        {
            _textBoxArea = Rectangle.Empty;
            _textArea = Rectangle.Empty;
            _allLines = new List<string>();
            _verticalScrollbar = new Scrollbar();
            _verticalScrollbar.Orientation = Orientation.Vertical;
            _verticalScrollbar.WriteToContent = false;
            _verticalScrollbar.Value = _verticalScrollbar.Maximum = _verticalScrollbar.Minimum = 0;
            _verticalScrollbar.Name = "InternalVerticalScroll";
            _verticalScrollbar.Parent = this;
            // TODO : change this to property changed
            //_verticalScrollbar.ValueChanged += new EventHandler(OnVerticalScrollbarValueChanged);
            Children.Add(_verticalScrollbar);
        }

        protected override void Resize()
        {
            if (LayoutSuspended)
            {
                return;
            }

            UpdateRectangles();
            int scrollBarWidth = WindowsService.Instance.Theme.Scrollbar.Rectangles["ScrollUp"].Width;
            FindComponent("InternalVerticalScroll").Position = new Rectangle(DrawRectangle.Width - scrollBarWidth, 0, scrollBarWidth, DrawRectangle.Height);
            _textBoxArea = DrawRectangle;
            _textBoxArea.Width -= scrollBarWidth;
            _textArea = _textBoxArea;
            _textArea.X += (int)Padding.X;
            _textArea.Y += (int)Padding.Y;
            _textArea.Width -= (int)(Padding.X + Padding.Z);
            _textArea.Height -= (int)(Padding.Y + Padding.W);

            foreach (Control c in Children)
            {
                c.RaiseBubbleEvent(this, new BubbleEventArgs(BubbleEvent.Resize, null, null));
            }
        }

        protected override void OnMouseDown(MouseButtonMap map, MouseButtonMapEventArgs e)
        {
            Focus();
            _selectStart = _selectEnd = GetTextPosition(InputService.Instance.MouseManager.CurrentAxisPosition.ToPoint());
            Selecting = true;
        }

        protected override void OnMouseUp(MouseButtonMap map, MouseButtonMapEventArgs e)
        {
            Selecting = false;
        }

        protected override void OnKeyDown(KeyMap map, KeyMapEventArgs e)
        {
            string newText = Text;
            int lineposition = 0;
            int cursorPosition = 0;

            switch (map.MappedButtons[0])
            {
                case Keys.Home:
                    if (InputService.Instance.KeyboardManager.IsButtonDown(Keys.LeftControl) ||
                        InputService.Instance.KeyboardManager.IsButtonDown(Keys.RightControl))
                    {
                        _selectEnd = 0;
                        _verticalScrollbar.Value = _verticalScrollbar.Minimum;
                    }
                    else
                    {
                        GetLineFromPosition(_selectEnd, out lineposition, out cursorPosition);
                        cursorPosition = 0;
                        _selectEnd = GetTextPosition(lineposition, cursorPosition);
                    }

                    if (!InputService.Instance.KeyboardManager.IsButtonDown(Keys.LeftShift) &&
                        !InputService.Instance.KeyboardManager.IsButtonDown(Keys.RightShift))
                    {
                        _selectStart = _selectEnd;
                    }
                    break;
                case Keys.End:
                    if (InputService.Instance.KeyboardManager.IsButtonDown(Keys.LeftControl) ||
                        InputService.Instance.KeyboardManager.IsButtonDown(Keys.RightControl))
                    {
                        _selectEnd = Text.Length;
                    }
                    else
                    {
                        GetLineFromPosition(_selectEnd, out lineposition, out cursorPosition);
                        cursorPosition = _allLines[lineposition].Length;
                        _selectEnd = GetTextPosition(lineposition, cursorPosition);
                    }

                    if (!InputService.Instance.KeyboardManager.IsButtonDown(Keys.LeftShift) &&
                        !InputService.Instance.KeyboardManager.IsButtonDown(Keys.RightShift))
                    {
                        _selectStart = _selectEnd;
                    }
                    break;
                case Keys.Left:
                    _selectEnd -= 1;
                    _selectEnd = Math.Max(_selectEnd, 0);

                    if (!InputService.Instance.KeyboardManager.IsButtonDown(Keys.LeftShift) &&
                        !InputService.Instance.KeyboardManager.IsButtonDown(Keys.RightShift))
                    {
                        _selectStart = _selectEnd;
                    }
                    break;
                case Keys.Right:
                    _selectEnd += 1;
                    _selectEnd = Math.Min(_selectEnd, Text.Length);

                    if (!InputService.Instance.KeyboardManager.IsButtonDown(Keys.LeftShift) &&
                        !InputService.Instance.KeyboardManager.IsButtonDown(Keys.RightShift))
                    {
                        _selectStart = _selectEnd;
                    }
                    break;
                case Keys.PageDown:
                    GetLineFromPosition(_selectEnd, out lineposition, out cursorPosition);
                    lineposition = Math.Min(lineposition + _displayLines.Count, _allLines.Count - 1);
                    _verticalScrollbar.Value += _displayLines.Count;
                    cursorPosition = Math.Min(_allLines[lineposition].Length, cursorPosition);
                    _selectEnd = GetTextPosition(lineposition, cursorPosition);

                    if (!InputService.Instance.KeyboardManager.IsButtonDown(Keys.LeftShift) &&
                        !InputService.Instance.KeyboardManager.IsButtonDown(Keys.RightShift))
                    {
                        _selectStart = _selectEnd;
                    }
                    break;
                case Keys.PageUp:
                    GetLineFromPosition(_selectEnd, out lineposition, out cursorPosition);
                    lineposition = Math.Max(lineposition - _displayLines.Count, 0);
                    _verticalScrollbar.Value -= _displayLines.Count;
                    cursorPosition = Math.Min(_allLines[lineposition].Length, cursorPosition);
                    _selectEnd = GetTextPosition(lineposition, cursorPosition);

                    if (!InputService.Instance.KeyboardManager.IsButtonDown(Keys.LeftShift) &&
                        !InputService.Instance.KeyboardManager.IsButtonDown(Keys.RightShift))
                    {
                        _selectStart = _selectEnd;
                    }
                    break;
                case Keys.Up:
                    GetLineFromPosition(_selectEnd, out lineposition, out cursorPosition);
                    lineposition = Math.Max(lineposition - 1, 0);

                    // TODO : Measure the width of the text and find the closest match on the 
                    // new line.
                    cursorPosition = Math.Min(_allLines[lineposition].Length, cursorPosition);
                    _selectEnd = GetTextPosition(lineposition, cursorPosition);

                    if (!InputService.Instance.KeyboardManager.IsButtonDown(Keys.LeftShift) &&
                        !InputService.Instance.KeyboardManager.IsButtonDown(Keys.RightShift))
                    {
                        _selectStart = _selectEnd;
                    }
                    break;
                case Keys.Down:
                    GetLineFromPosition(_selectEnd, out lineposition, out cursorPosition);
                    lineposition = Math.Min(lineposition + 1, _allLines.Count - 1);

                    // TODO : see Keys.Up;
                    cursorPosition = Math.Min(_allLines[lineposition].Length, cursorPosition);
                    _selectEnd = GetTextPosition(lineposition, cursorPosition);

                    if (!InputService.Instance.KeyboardManager.IsButtonDown(Keys.LeftShift) &&
                        !InputService.Instance.KeyboardManager.IsButtonDown(Keys.RightShift))
                    {
                        _selectStart = _selectEnd;
                    }
                    break;
                case Keys.Back:
                    if (Locked)
                    {
                        break;
                    }

                    if (_selectEnd != _selectStart)
                    {
                        newText = Text.Remove(Math.Min(_selectStart, _selectEnd), Math.Abs(_selectEnd - _selectStart));
                        _selectStart = _selectEnd = Math.Min(_selectStart, _selectEnd);
                    }
                    else if (_selectStart > 0)
                    {
                        newText = Text.Remove(_selectStart - 1, 1);
                        _selectStart = _selectEnd -= 1;
                    }
                    break;
                case Keys.Delete:
                    if (Locked)
                    {
                        break;
                    }

                    if (_selectEnd != _selectStart)
                    {
                        newText = Text.Remove(Math.Min(_selectStart, _selectEnd), Math.Abs(_selectEnd - _selectStart));
                        _selectStart = _selectEnd = Math.Min(_selectStart, _selectEnd);
                    }
                    else
                    {
                        if (_selectStart < Text.Length)
                        {
                            newText = Text.Remove(_selectStart, 1);
                        }
                    }
                    break;
                default:
                    if (InputService.Instance.KeyboardManager.IsButtonDown(Keys.LeftControl) ||
                        InputService.Instance.KeyboardManager.IsButtonDown(Keys.RightControl))
                    {
                        switch (map.MappedButtons[0])
                        {
                            case Keys.C:
                                WindowsService.Instance.Clipboard.Text = this.SelectedText();
                                break;
                            case Keys.V:
                                if (Locked)
                                {
                                    break;
                                }

                                newText = Text.Remove(Math.Min(_selectStart, _selectEnd), Math.Abs(_selectEnd - _selectStart));
                                _selectStart = _selectEnd = Math.Min(_selectStart, _selectEnd);
                                string clipBoardText = WindowsService.Instance.Clipboard.Text;
                                newText = newText.Insert(_selectEnd, clipBoardText);
                                _selectStart = _selectEnd += clipBoardText.Length;
                                break;
                            case Keys.X:
                                WindowsService.Instance.Clipboard.Text = this.SelectedText();

                                if (Locked)
                                {
                                    break;
                                }

                                newText = Text.Remove(Math.Min(_selectStart, _selectEnd), Math.Abs(_selectEnd - _selectStart));
                                _selectStart = _selectEnd = Math.Min(_selectStart, _selectEnd);
                                break;
                            case Keys.A:
                                _selectStart = 0;
                                _selectEnd = Text.Length;
                                break;
                        }
                    }
                    else
                    {
                        if (Locked)
                        {
                            break;
                        }
                        
                        string key = string.Empty;

                        if (!InputService.Instance.KeyboardManager.IsButtonDown(Keys.LeftShift) &&
                            !InputService.Instance.KeyboardManager.IsButtonDown(Keys.RightShift))
                        {
                            key = KeyToStringConverter.Convert(map.MappedButtons[0], false);
                        }
                        else
                        {
                            key = KeyToStringConverter.Convert(map.MappedButtons[0], true);
                        }

                        if (map.MappedButtons[0] == Keys.Enter)
                        {
                            key = "\r\n";
                        }

                        if (key == "")
                        {
                            break;
                        }

                        newText = Text.Remove(Math.Min(_selectStart, _selectEnd), Math.Abs(_selectEnd - _selectStart));
                        _selectStart = _selectEnd = Math.Min(_selectStart, _selectEnd);
                        newText = newText.Insert(_selectEnd, key);
                        _selectStart = _selectEnd += 1;

                        if (map.MappedButtons[0] == Keys.Enter)
                        {
                            _selectStart = _selectEnd += 1;
                        }
                    }
                    break;
            }

            CursorTime = 500;
            Text = newText;
            ScrollToCursor();
            base.OnKeyDown(map, e);
        }

        protected override void OnMouseScroll(MouseScrollWheelMap map, MouseScrollWheelMapEventArgs e)
        {
            _verticalScrollbar.Value += e.PositionDelta;
            base.OnMouseScroll(map, e);
        }

        private void OnVerticalScrollbarValueChanged(object sender, EventArgs e)
        {
            if (WindowsService.Instance == null)
            {
                return;
            }

            _displayText = DisplayText();
        }

        private void UpdateAllLines()
        {
            if (WindowsService.Instance.Theme != null)
            {
                Font font = WindowsService.Instance.Theme.Fonts[Font];
                int size = FontSize;
                Vector2 measure = Vector2.Zero;
                string remains = Text;
                string currentChar;
                string currentLine = string.Empty;
                string nextLine = string.Empty;
                _allLines.Clear();

                while (remains.Length > 0)
                {
                    currentChar = remains.Substring(0, 1);
                    remains = remains.Substring(1, remains.Length - 1);

                    if (currentChar == "\n")
                    {
                        _allLines.Add(currentLine);
                        currentLine = string.Empty;
                        continue;
                    }

                    measure = font.MeasureString(size, currentLine + currentChar);

                    // These five extra pixels are here to help solve weird line wrapping problems.
                    if (measure.X <= _textArea.Width - 5)
                    {
                        currentLine += currentChar;
                        nextLine = string.Empty;
                    }
                    else
                    {
                        int whiteIndex = currentLine.LastIndexOfAny(new char[] { '\n', '\t', ' ' });

                        if (whiteIndex == -1)
                        {
                            nextLine = currentChar;
                        }
                        else
                        {
                            nextLine = currentLine.Substring(whiteIndex + 1) + currentChar;
                            currentLine = currentLine.Substring(0, whiteIndex);
                        }

                        _allLines.Add(currentLine);
                        currentLine = nextLine;
                    }
                }

                _allLines.Add(currentLine);
            }
        }

        private string ListStringToString(List<String> data)
        {
            if (data.Count < 1)
            {
                return string.Empty;
            }

            string result = data[0];

            for (int i = 1; i < data.Count; i++)
            {
                result += "\n" + data[i];
            }

            return result;
        }

        private int GetTextPosition(Point position)
        {
            if (_allLines.Count < 1)
            {
                return 0;
            }

            int line = (int)Math.Floor((double)(position.Y - _textArea.Y) / _lineHeight);
            line = Math.Max(line, 0);
            line += _verticalScrollbar.Value;
            int charPosition = 0;

            if (line > _allLines.Count - 1)
            {
                line = Math.Min(_allLines.Count - 1, line);
                charPosition = _allLines[line].Length;
            }
            else
            {
                if (_allLines[line].Trim() == string.Empty)
                {
                    charPosition = 0;
                }
                else
                {
                    charPosition = WindowsService.Instance.Theme.Fonts[Font].CursorPosition(_allLines[line], position, _textArea, FontSize);
                }
            }

            return GetTextPosition(line, charPosition);
        }

        private int GetTextPosition(int line, int charPosition)
        {
            int result = 0;

            for (int i = 0; i < _allLines.Count && i < line; i++)
            {
                result += _allLines[i].Length + 1;
            }

            result += charPosition;
            return result;
        }

        private void GetLineFromPosition(int position, out int line, out int charPosition)
        {
            int actualPosition = position;
            line = 0;

            for (int i = 0; i < _allLines.Count; i++)
            {
                if (actualPosition >= _allLines[i].Length + 1)
                {
                    line++;
                    actualPosition -= (_allLines[i].Length + 1);
                }
                else
                {
                    break;
                }
            }

            charPosition = actualPosition;
        }

        private string SelectedText()
        {
            int start = Math.Min(_selectStart, _selectEnd);
            int len = Math.Max(_selectStart, _selectEnd) - start;
            return Text.Substring(start, len);
        }

        private string DisplayText()
        {
            if (Text.Trim() == string.Empty)
            {
                return string.Empty;
            }

            int lineCount = (int)Math.Floor((double)_textArea.Height / (double)FontSize);
            _verticalScrollbar.Maximum = Math.Max(_allLines.Count - lineCount, 0);
            int startline = _verticalScrollbar.Value;
            _displayLines.Clear();

            for (int i = startline; i < (lineCount + startline) && i < _allLines.Count; i++)
            {
                _displayLines.Add(_allLines[i]);
            }

            string result = ListStringToString(_displayLines);

            _lineHeight = WindowsService.Instance.Theme.Fonts[Font].MeasureString(FontSize, result).Y / _displayLines.Count;
            return result;
        }

        private void ScrollToCursor()
        {
            int cursorpos = 0;
            int cursorline = 0;
            GetLineFromPosition(_selectEnd, out cursorline, out cursorpos);

            while (cursorline > _displayLines.Count - 1 && _verticalScrollbar.Value < _verticalScrollbar.Maximum)
            {
                _verticalScrollbar.Value += 1;
                GetLineFromPosition(_selectEnd, out cursorline, out cursorpos);
            }

            while (cursorline < _verticalScrollbar.Value && _verticalScrollbar.Value > _verticalScrollbar.Minimum)
            {
                _verticalScrollbar.Value -= 1;
            }
        }

        #region IDrawable Members

        public override void Draw(GameTime gameTime)
        {
            if (Visible)
            {
                if (Text != string.Empty && _displayText == string.Empty)
                {
                    UpdateAllLines();
                    _displayText = DisplayText();
                }

                int cursorPosition;
                int cursorLine;
                int startPosition;
                int startLine;
                int endPosition;
                int endLine;
                Rectangle lineRectangle = _textArea;

                WindowsService.Instance.Theme.Textbox.Draw("Default", _textBoxArea, Color);
                WindowsService.Instance.Theme.Fonts[Font].Draw(_displayText, _textArea, FontSize, FontColor);
                GetLineFromPosition(_selectEnd, out cursorLine, out cursorPosition);
                cursorLine -= _verticalScrollbar.Value;
                GetLineFromPosition(Math.Min(_selectStart, _selectEnd), out startLine, out startPosition);
                startLine -= _verticalScrollbar.Value;
                GetLineFromPosition(Math.Max(_selectEnd, _selectStart), out endLine, out endPosition);
                endLine -= _verticalScrollbar.Value;
                lineRectangle.Height = FontSize;

                if (_displayLines.Count == 0 && this.HasFocus && (CursorBlink || CursorTime > 0))
                {
                    WindowsService.Instance.Theme.Fonts[this.Font].Draw(endPosition, "", lineRectangle, FontColor, FontSize);
                }
                else
                {
                    for (int i = 0; i < _displayLines.Count; i++)
                    {
                        int sp = startPosition;
                        int ep = endPosition;

                        if (Text.Trim() != string.Empty)
                        {
                            Font font = WindowsService.Instance.Theme.Fonts[Font];

                            if (i == startLine)
                            {
                                if (startLine != endLine)
                                {
                                    ep = _displayLines[i].Length;
                                }

                                Vector2 startVector = font.MeasureString(FontSize, _displayLines[i].Substring(0, sp));
                                Vector2 endVector = font.MeasureString(FontSize, _displayLines[i].Substring(0, ep));
                                string highlightText = _displayLines[i].Substring(Math.Min(sp, ep), Math.Abs(sp - ep));
                                Rectangle highlightArea = new Rectangle(lineRectangle.X + (int)Math.Ceiling(Math.Min(startVector.X, endVector.X)),
                                    lineRectangle.Y,
                                    (int)Math.Ceiling(Math.Abs((endVector.X - startVector.X))),
                                    (int)Math.Max(endVector.Y, startVector.Y));
                                highlightArea.Width -= Math.Max(highlightArea.Right - lineRectangle.Right, 0);
                                WindowsService.Instance.DrawRectangle(highlightArea, BackColorHighlight);
                                highlightArea.Width += 1;
                                WindowsService.Instance.Theme.Fonts[Font].Draw(highlightText, highlightArea, FontSize, ForeColorHighlight);
                            }
                            else if (i == endLine)
                            {
                                if (startLine != endLine)
                                {
                                    sp = 0;
                                }

                                Vector2 startVector = font.MeasureString(FontSize, _displayLines[i].Substring(0, sp));
                                Vector2 endVector = font.MeasureString(FontSize, _displayLines[i].Substring(0, ep));
                                string highlightText = _displayLines[i].Substring(Math.Min(sp, ep), Math.Abs(sp - ep));
                                Rectangle highlightArea = new Rectangle(lineRectangle.X + (int)Math.Ceiling(Math.Min(startVector.X, endVector.X)),
                                    lineRectangle.Y,
                                    (int)Math.Ceiling(Math.Abs((endVector.X - startVector.X))),
                                    (int)Math.Max(endVector.Y, startVector.Y));
                                highlightArea.Width -= Math.Max(highlightArea.Right - lineRectangle.Right, 0);
                                WindowsService.Instance.DrawRectangle(highlightArea, BackColorHighlight);
                                highlightArea.Width += 1;
                                WindowsService.Instance.Theme.Fonts[Font].Draw(highlightText, highlightArea, FontSize, ForeColorHighlight);
                            }
                            else if (i > startLine && i < endLine)
                            {
                                sp = 0;
                                ep = _displayLines[i].Length;
                                Vector2 startV = font.MeasureString(FontSize, _displayLines[i].Substring(0, sp));
                                Vector2 endV = font.MeasureString(FontSize, _displayLines[i].Substring(0, ep));
                                string highlightText = _displayLines[i].Substring(Math.Min(sp, ep), Math.Abs(sp - ep));
                                Rectangle highlightArea = new Rectangle(lineRectangle.X + (int)Math.Ceiling(Math.Min(startV.X, endV.X)),
                                    lineRectangle.Y,
                                    (int)Math.Ceiling(Math.Abs((endV.X - startV.X))),
                                    (int)Math.Max(endV.Y, startV.Y));
                                highlightArea.Width -= Math.Max(highlightArea.Right - lineRectangle.Right, 0);
                                WindowsService.Instance.DrawRectangle(highlightArea, BackColorHighlight);
                                highlightArea.Width += 1;
                                WindowsService.Instance.Theme.Fonts[Font].Draw(highlightText, highlightArea, FontSize, ForeColorHighlight);
                            }
                        }

                        if (i == cursorLine && HasFocus && (CursorBlink || CursorTime > 0))
                        {
                            WindowsService.Instance.Theme.Fonts[Font].Draw(cursorPosition, _displayLines[i], lineRectangle, FontColor, FontSize);
                        }

                        lineRectangle.Y = _textArea.Y + (int)(_lineHeight * (i + 1));
                    }
                }

                DrawChildren(gameTime);
            }
        }

        #endregion

        #region IUpdateable Members

        public override void Update(GameTime gameTime)
        {
            if (Enabled)
            {
                if (_scrollSleep > 0)
                {
                    _scrollSleep -= gameTime.ElapsedRealTime.Milliseconds;
                }

                CursorBlink = ((gameTime.TotalRealTime.Milliseconds % 1000) >= 500);
                CursorTime = Math.Max(0, CursorTime - gameTime.ElapsedGameTime.Milliseconds);

                if (Selecting)
                {
                    if ((InputService.Instance.MouseManager.CurrentAxisPosition.Y < _textArea.Y) && _scrollSleep <= 0)
                    {
                        _verticalScrollbar.Value -= 1;
                        _scrollSleep = 250;
                    }

                    if (InputService.Instance.MouseManager.CurrentAxisPosition.Y > (_textArea.Y + (_displayLines.Count * _lineHeight)) && _scrollSleep <= 0)
                    {
                        _verticalScrollbar.Value += 1;
                        _scrollSleep = 250;
                    }

                    _selectEnd = GetTextPosition(InputService.Instance.MouseManager.CurrentAxisPosition.ToPoint());
                    _selectEnd = Math.Max(_selectEnd, 0);
                    CursorTime = 500;
                }

                base.Update(gameTime);
            }
        }

        #endregion
    }
}
